#include "Leaf.h"

Leaf::Leaf(BufferFrame& page, NodeContext& nodeContext) : page(page), header(*(NodeHeader*)(page.getData())), context(nodeContext)
{
}


Leaf Leaf::fromNew(BufferFrame& page, NodeContext& nodeContext)
{
    // An dieser Stelle muss sofort die Page mit einem Initial-Header beschrieben werden, dieser wird im Konstruktor nämlich ausgelesen.
    NodeHeader& header = *(NodeHeader*)(page.getData());
    header.isLeaf = true;
    header.hasNext = false; // TODO: Muss irgendwo noch gesetzt werden
    header.entryCount = 0;
    return Leaf(page, nodeContext);
}

Leaf Leaf::fromExisting(BufferFrame& page, NodeContext& nodeContext)
{
    return Leaf(page, nodeContext);
}


/**
 * Fügt einen Eintrag hinzu. Dabei kann es vorkommen, dass der Knoten gesplittet wird. Der bestehende Knoten wird zum linken Knoten, ein neu erzeugter wird zum rechten Knoten.
 * Die PageIds innerhalb des Segments werden innerhalb des SplitOps zurückgegeben.
 * Im Falle des Split muss der Speicher für den Key, auf den SplitOp.key zeigt, wieder freigegeben werdem!
 * Nach Beendigung dieser Funktion sind alle Locks auf die hierin beschriebenen Seiten wieder aufgehoben.
 */

SplitOp Leaf::insert(char* key, TID tid)
{
    // TODO: Wenn key schon vorhanden ist => muss was schlimmes passieren ;-) (bisher keine Überprüfung)

    SplitOp result;
    result.splitPerformed = false;

    unsigned useableSize = pageSize - sizeof(NodeHeader) - (this->header.entryCount * (context.keyLen + sizeof(TID)));
    NodeHeader& header = *((NodeHeader*)(this->page.getData()));

    // Kein Split erforderlich
    if (useableSize >= (context.keyLen + sizeof(TID))) {
        // einfach so reinkopieren, dabei Daten, die größer sind, nach rechts verschieben
        this->insertToRightPosition(key, tid, ((char*)(this->page.getData())) + sizeof(NodeHeader), header.entryCount);
        header.entryCount += 1;

        context.segment.unfixPage(this->page, true);
        return result;
    }

    // Muss gesplittet werden?
    vector<unsigned> res = context.segment.requestPages(1,1);
    unsigned newLeafId = res[0];

    BufferFrame& newLeafPage = context.segment.getPage(newLeafId, true);
    // header erzeugen
    Leaf newLeaf = Leaf::fromNew(newLeafPage, context);


    // Daten temporär im Speicher ablegen, und den neuen Eintrag entsprechend einsortieren
    char* overfullPageCopy = (char*)malloc((header.entryCount + 1) * (context.keyLen + sizeof(TID)));

    // Daten in neue Seite kopieren
    unsigned len = header.entryCount * (context.keyLen + sizeof(TID));
    memcpy(overfullPageCopy, ((char*)(this->page.getData())) + sizeof(NodeHeader), len);

    // Neues Element kopieren
    this->insertToRightPosition(key, tid, overfullPageCopy, header.entryCount);


    // Jetzt liegt an overfullPageCopy der gesamte fertige Knoten, aber der passt nicht auf eine Seite.

     // Daten rüberkopieren
    unsigned entriesForLeftNode = ((header.entryCount) / 2) + 1;

    // Linker Knoten
    unsigned leftLen = entriesForLeftNode * (context.keyLen + sizeof(TID));
    memcpy(((char*)(this->page.getData())) + sizeof(NodeHeader), overfullPageCopy, leftLen);

    // Als SplitKey wird der (entriesForLeftNode + 1) Key genommen.
    result.key = (char*)malloc(context.keyLen);
    memcpy(result.key, overfullPageCopy + leftLen, context.keyLen);

    // Rechter Knoten
    unsigned rightLen = ((header.entryCount+1) - entriesForLeftNode) * (context.keyLen + sizeof(TID));
    memcpy(((char*)(newLeafPage.getData())) + sizeof(NodeHeader), overfullPageCopy + leftLen, rightLen);

    free(overfullPageCopy);

    NodeHeader& newLeafHeader = *((NodeHeader*)(newLeafPage.getData()));

    newLeafHeader.entryCount = (header.entryCount+1) - entriesForLeftNode;

    newLeafHeader.hasNext = header.hasNext;
    newLeafHeader.nextPageId = header.nextPageId;
    header.entryCount = entriesForLeftNode;
    header.hasNext = true;
    header.nextPageId = newLeafId;

    result.splitPerformed = true;
    result.newPageId = newLeafId;

    context.segment.unfixPage(this->page, true);
    context.segment.unfixPage(newLeafPage, true);
    return result;
}


/**
 * Fügt einen Eintrag so ein, dass danach wieder alles sortiert ist.
 * Es wird davon ausgegangen, dass genug Platz vorhanden ist.
 * Am Ende werden keine Locks zurückgegeben!
 */
void Leaf::insertToRightPosition(char* key, TID tid, char* dataStart, unsigned dataCount) {

    // Zeigt an die Speicherzelle hinter dem letzten Element
    char* endPosition = dataStart + (dataCount * (context.keyLen + sizeof(TID)));

    // Alle größeren Datensätze nach rechts verschieben
    for (unsigned i=0; i<header.entryCount; i++) {
        if (context.compare(SMALLEROREQUAL, endPosition - (context.keyLen + sizeof(TID)), key)) break;

        // Datensatz nach hinten verschieben
        memcpy(endPosition, endPosition - (context.keyLen + sizeof(TID)), context.keyLen + sizeof(TID));
        endPosition -= context.keyLen + sizeof(TID);
    }

    // Stelle, an der der Datensatz eingefügt wird
    memcpy(endPosition, key, context.keyLen);
    *((TID*)(endPosition + context.keyLen)) = tid;
}


TID Leaf::lookup(char* key)
{
    char* currentPosition = ((char*)(this->page.getData())) + sizeof(NodeHeader);

    for (unsigned i = 0; i < header.entryCount; i++)
    {
        if (context.compare(EQUAL, currentPosition, key))
        {
			TID* tid = (TID*)(currentPosition + context.keyLen);
            return *tid;
        }
        currentPosition += sizeof(TID) + context.keyLen;
    }

	cout << "Something bad happened: Index does not contain key." << endl;
    exit(1);
	return TID(); //element not found...
}

index_iterator Leaf::lookupRange(char* lower, char* upper)
{
    vector<TID> iterContent;

    char* currentPosition = (char*)page.getData() + sizeof(NodeHeader);
    unsigned count = 0;
    bool foundOne = false;

    while (count < header.entryCount)
    {
        //cout << "key-leaf: " << *(unsigned*)currentPosition << endl;
        if (context.compare(GREATEROREQUAL, currentPosition, lower) && context.compare(SMALLEROREQUAL, currentPosition, upper))
        {
            TID* tid = (TID*)(currentPosition + context.keyLen);
            iterContent.push_back(*tid);
            foundOne = true;
        }
        else if(foundOne)
        {   //Es wurde bereits einer gefunden, wenn das Suchkriterium nun nicht mehr passt wird man auch nichts mehr finden
            break;
        }
        count++;
        currentPosition += context.keyLen + sizeof(TID);
    }

    index_iterator iter(iterContent);
    //unlock here
    context.segment.unfixPage(page, false);

    if(foundOne && header.hasNext)
    {   //Im nächsten Knoten findet man vlt au no was
        Node next(header.nextPageId, context);
        iter.add(std::move(next.lookupRange(lower, upper)));
    }

    return iter;
}

RemovalResult* Leaf::remove(char* key, bool isRoot)
{   //finde Eintrag
    char* currentPosition = (char*)page.getData() + sizeof(NodeHeader);
    int position = -1;

    leafBla* debug = (leafBla*)page.getData();

    for (unsigned i = 0; i < header.entryCount; i++)
    {
        if (context.compare(EQUAL, currentPosition, key))
        {   //gefunden
			position = i;
			break;
        }
        currentPosition += sizeof(TID) + context.keyLen;
    }

    if(position == -1)
    {   //Eintrag war nicht vorhanden
        return new RemovalResult();
    }

    //Kopiere zu behaltende Daten hinter dem Eintrag
    unsigned tmpSize = (header.entryCount - position - 1) * (sizeof(TID) + context.keyLen);
    char* tmpData = new char[tmpSize];
    char* pagePointer = (char*)page.getData() + sizeof(NodeHeader) + (position + 1) * (sizeof(TID) + context.keyLen);
    memcpy(tmpData, pagePointer, tmpSize);

    //Kopiere Daten zurück und überschreibe dabei den alten Eintrag
    pagePointer -= (sizeof(TID) + context.keyLen);
    memcpy(pagePointer, tmpData, tmpSize);
    delete[] tmpData;

    header.entryCount--;

    RemovalResult* result = new RemovalResult();
    result->removed = true;
    result->nowEmpty = header.entryCount == 0;
    result->newKey = (position == 0)?((char*)page.getData() + sizeof(NodeHeader)):NULL;
    result->lastOneDeleted = !header.hasNext;
    result->redirectNext = result->nowEmpty;
    result->newNext = header.nextPageId;
    return result;
}


void Leaf::visualize(IndexVisualizer& vis, unsigned pageId)
{
    string nodeDes = "node" + std::move(IndexVisualizer::toString(pageId)) + " [shape=record, label=\"<id> " + IndexVisualizer::toString(pageId) + " | <count> " + std::move(IndexVisualizer::toString(header.entryCount)) + " | <isLeaf> true | ";
    char* currentPosition = (char*)page.getData() + sizeof(NodeHeader);

    const unsigned maxCount = getMaxEntryCount();
//cout << "leaf" << endl;
    for(unsigned i = 0; i < maxCount; i++)
    {
        string keyDes = "<key" + std::move(IndexVisualizer::toString(i)) + "> ";
        string keyVal = "";
        string valDes = "<tid" + std::move(IndexVisualizer::toString(i)) + "> ";
        string valVal = "";

        if(i < header.entryCount)
        {
            char* key = currentPosition;
            TID* tid = (TID*)(currentPosition + context.keyLen);
            keyVal = vis.keyToString(key);
            valVal = IndexVisualizer::toString(*tid);
        }

        nodeDes += std::move(keyDes) + std::move(keyVal) + " | " + std::move(valDes) + std::move(valVal) + " | ";
        currentPosition += context.keyLen + sizeof(TID);
    }

    nodeDes += "<next> ";

    if(header.hasNext)
    {
        nodeDes += "*";
        unsigned nextPageId = header.nextPageId;
        vis.addNextRelation(pageId, nextPageId);
    }

    nodeDes += "\"];";
    vis.addNode(nodeDes);

    //leaf2 [shape=record, label="<count> 3 | <isLeaf> true | <key0> NL | <key1> NZ | <key2> NO | <key3> | <tid0> 4711:2 | <tid1> 4:11 | <tid2> 101:8 | <tid3> | <next> *"];
}

unsigned Leaf::getMaxEntryCount()
{
	return (pageSize - sizeof(NodeHeader)) / (context.keyLen + sizeof(TID));
}
